﻿using System;
using System.Net;
using System.Linq;
using System.Windows;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Net.Http;
using System.Text;
using Windows.Security.Authentication.Web;
using System.Net.Shared;

namespace MetroDropBox.Shared
{
    public abstract class oAuthClientBase
    {

        protected OAuthToken RequestToken { get; set; }
        protected OAuthToken AccessToken { get; set; }
        protected string _apiKey;
        protected string _apiSecret;
        protected string authHeaderRealm = "";
        
        protected bool passOAuthTokenByDefault = true;
        protected bool getRequestTokenParamsInPostContent = false;
        protected bool useAuthHeaderForHttpUris = false;
        protected bool useSha1SigningForHttpUris = false;
        protected bool useSha1Signing = true;
        protected bool usesAuthHeader = false;
        protected bool upperCaseUrlEncoding = false;
        protected bool usesOAuthVerifier = true;
        protected string getRequestTokenURL;
        protected string getRequestTokenMethod = "POST";
        protected string getUserLoginURL;
        protected string getAuthTokenURL;
        protected string authPageJsTweaks;
        public oAuthClientBase(string apiKey, string apiSecret)
        {
            _apiKey = apiKey;
            _apiSecret = apiSecret;
            init();
            
        }
        
        protected abstract void init();
        protected virtual void injectAdditionalParams(string url, Dictionary<string, string> parms) { }

        public virtual async Task<String> GetLoginUrlAsync(string redirectUrl)
        {
            if (RequestToken == null)
            {
                var requestToken = await GetRequestTokenAsync(new Dictionary<String, String>(), redirectUrl);
                RequestToken = requestToken;
            }
            if (RequestToken != null && !String.IsNullOrEmpty(RequestToken.Token))
                return getUserLoginURL + "?oauth_token=" + RequestToken.Token;
            return "";
        }


        #region oAuth signing

        const string OAuthVersion = "1.0";

        //private async Task<string> httpPostForString(string url, string callbackUrl)
        //{
        //    TimeSpan SinceEpoch = (DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime());
        //    Random Rand = new Random();
        //    Int32 Nonce = Rand.Next(1000000000);

        //    //
        //    // Compute base signature string and sign it.
        //    //    This is a common operation that is required for all requests even after the token is obtained.
        //    //    Parameters need to be sorted in alphabetical order
        //    //    Keys and values should be URL Encoded.
        //    //
        //    String SigBaseStringParams = "oauth_callback=" + urlEncode(callbackUrl);
        //    SigBaseStringParams += "&" + "oauth_consumer_key=" + _apiKey;
        //    SigBaseStringParams += "&" + "oauth_nonce=" + Nonce.ToString();
        //    SigBaseStringParams += "&" + "oauth_signature_method=HMAC-SHA1";
        //    SigBaseStringParams += "&" + "oauth_timestamp=" + Math.Round(SinceEpoch.TotalSeconds);
        //    SigBaseStringParams += "&" + "oauth_version=1.0";

        //    String SigBaseString = "POST&";
        //    SigBaseString += urlEncode(url) + "&" + urlEncode(SigBaseStringParams);
        //    Debug.WriteLine("SigBaseString: " + SigBaseString);
        //    Debug.WriteLine("key: " + string.Format("{0}&", urlEncode(_apiSecret)));
        //    var signature = SHA1EncryptionHelper.HMACSha1Sign(string.Format("{0}&", urlEncode(_apiSecret)), SigBaseString);

        //    String authHeader = "OAuth oauth_callback=\"" + urlEncode(callbackUrl) + "\", oauth_consumer_key=\"" + _apiKey + "\", oauth_nonce=\"" + Nonce.ToString() + "\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"" + Math.Round(SinceEpoch.TotalSeconds) + "\", oauth_version=\"1.0\", oauth_signature=\"" + urlEncode(signature) + "\"";

        //    Debug.WriteLine(String.Format("posting to --> {0} Headers:{1}", url, authHeader));

        //    string result;

        //    try
        //    {
        //        result = await HttpRequestHelper.simpleHttpPost(url, authHeader);
        //        Debug.WriteLine(String.Format("getRequestToken --> {0}", result));
        //        return result;
        //    }
        //    catch (WebException ex)
        //    {
        //    }
        //    return "";
        //}

        private Dictionary<String, String> injectOAuthSignatureIntoParms(string method, string resource_url, Dictionary<String, String> originalParams = null, OAuthToken request_token = null, bool passOAuthToken = true)
        {
            //var test = new OAuth.OAuthBase();

            var parms = new Dictionary<String, String>(originalParams);
            injectAdditionalParams(resource_url, parms);

            var useSha1 = useSha1Signing || (useSha1SigningForHttpUris && resource_url.StartsWith("http:", StringComparison.OrdinalIgnoreCase));

            Debug.WriteLine(String.Format("Token used is: {0}", (request_token!=null?"passed token":(RequestToken != null?"Request Token":"Auth Token"))));

            var objToken = request_token ?? RequestToken ?? AccessToken;
            String token = "";

            String secret = "";
            if (objToken != null)
            {
                token = objToken.Token;
                secret = objToken.Secret;
            }

            Debug.WriteLine("Date Right now: {0:yyyy-MM-dd hh:mm:ss} UTC", DateTime.UtcNow);
            TimeSpan sinceEpoch = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc));

            Random Rand = new Random();
            Int32 nonce = Rand.Next(1000000000);

            var altParamsString = dictionaryToQSStringBuilder(originalParams);
            string normalUri, normalReqParams;
            //var altSignature = test.GenerateSignature(new Uri(resource_url + (altParamsString.Length <= 0 ? "" : "?" + altParamsString), UriKind.Absolute), _apiKey, 
            //                _apiSecret, (passOAuthToken ? token : null), secret, method, ((Int32)Math.Round(sinceEpoch.TotalSeconds)).ToString(), nonce.ToString(), out normalUri, out normalReqParams);
            //Debug.WriteLine("normalUri: " + normalUri);
            //Debug.WriteLine("normalReqParams: ");
            //for (var i = 0; i < normalReqParams.Length; i = i + 120)
            //{
            //    var output = (i + 120 >= normalReqParams.Length ? normalReqParams.Substring(i) : normalReqParams.Substring(i, 120));
            //    Debug.WriteLine(output);
            //}
            
            // Add additional oAuth Parms that will need to be included in the signature
            parms["oauth_consumer_key"] = _apiKey;
            if (objToken != null && !parms.ContainsKey("oauth_token") && passOAuthToken)
                parms["oauth_token"] = token;


            parms["oauth_nonce"] = nonce.ToString();
            parms["oauth_timestamp"] = ((Int32)Math.Round(sinceEpoch.TotalSeconds)).ToString();

            parms["oauth_signature_method"] = useSha1 ? "HMAC-SHA1" : "PLAINTEXT";
            parms["oauth_version"] = OAuthVersion;

            if (!useSha1)
            {
                //throw new NotSupportedException("Plain Text Signing not allowed with Twitter");
                parms["oauth_signature"] = string.Format("{0}&{1}", urlEncode(_apiSecret), urlEncode(secret)); // urlDecode("%26");
            }
            else
            {
                //
                // Compute base signature string and sign it.
                //    This is a common operation that is required for all requests even after the token is obtained.
                //    Parameters need to be sorted in alphabetical order
                //    Keys and values should be URL Encoded.
                //
                var qsStringParams = new List<String>();

                foreach (var kvp in parms)
                {
                    String strItem;
                    if (kvp.Key.StartsWith("oauth"))
                    {
                        if (!kvp.Key.Equals("oauth_callback"))
                            strItem = String.Format("{0}={1}", kvp.Key, kvp.Value);
                        else
                            strItem = String.Format("{0}={1}", kvp.Key, urlEncode(kvp.Value));
                    }
                    else
                        strItem = String.Format("{0}={1}", kvp.Key, urlEncode(kvp.Value));
                    qsStringParams.Add(strItem);
                }

                var qsString = String.Join("&", qsStringParams.OrderBy(i => i).ToArray());

                String SigBaseStringParams = qsString;
                String SigBaseString = method.ToUpper() + "&";

                var uri = new Uri(resource_url);
                var normalizedUrl = string.Format("{0}://{1}", uri.Scheme, uri.Host);

                if (!((uri.Scheme == "http" && uri.Port == 80) || (uri.Scheme == "https" && uri.Port == 443)))
                    normalizedUrl += ":" + uri.Port;
                normalizedUrl += uri.AbsolutePath;

                SigBaseString += urlEncode(normalizedUrl) + "&" + urlEncode(SigBaseStringParams);

                Debug.WriteLine("generating signature against: ");
                Debug.WriteLine("---------------- ");
                var signatureBaseItems = SigBaseString.Split('&');
                foreach (var sigBaseItem in signatureBaseItems)
                {
                    if (sigBaseItem.Length < 120)
                        Debug.WriteLine(sigBaseItem + "&");
                    else
                        for (int i = 0; i < sigBaseItem.Length; i = i + 120)
                        {
                            if (i + 120 < sigBaseItem.Length)
                                Debug.WriteLine(sigBaseItem.Substring(i, 120));
                            else
                                Debug.WriteLine(sigBaseItem.Substring(i) + "&");
                        }
                }
                //Debug.WriteLine("---------------- ");
                //Debug.WriteLine("Alt signature: ");
                //signatureBaseItems = altSignature.Split('&');
                //foreach (var sigBaseItem in signatureBaseItems)
                //{
                //    if (sigBaseItem.Length < 120)
                //        Debug.WriteLine(sigBaseItem + ",");
                //    else
                //        for (int i = 0; i < sigBaseItem.Length; i = i + 120)
                //        {
                //            if (i + 120 < sigBaseItem.Length)
                //                Debug.WriteLine(sigBaseItem.Substring(i, 120));
                //            else
                //                Debug.WriteLine(sigBaseItem.Substring(i) + "&");
                //        }
                //}
                Debug.WriteLine("---------------- ");
                Debug.WriteLine("Hash key is: " + string.Format("{0}&{1}", urlEncode(_apiSecret), urlEncode(secret)));
                var signature = SHA1EncryptionHelper.HMACSha1Sign(string.Format("{0}&{1}", urlEncode(_apiSecret), urlEncode(secret)), SigBaseString);
                Debug.WriteLine("Hash result is: " + signature);
                parms["oauth_signature"] = signature;
            }

            return parms;

        }

        private StringBuilder dictionaryToOAuthHeaderStringBuilder(Dictionary<String, String> parms, bool passOAuthToken = true)
        {

            var qsString = new StringBuilder("OAuth ");
            bool first = true;
            foreach (var parm in parms.Where(i => i.Key.StartsWith("oauth") && !i.Key.Equals("oauth_signature")).OrderBy(i => i.Key))
            {
                //if (!passOAuthToken && parm.Key.Equals("oauth_token"))
                //    continue;

                qsString.AppendFormat("{0}{1}=\"{2}\"", first ? "" : ", ", urlEncode(parm.Key), urlEncode(parm.Value));
                first = false;
            }
            var sig = parms.Where(i => i.Key.Equals("oauth_signature")).FirstOrDefault();
            if (sig.Value != null)
                qsString.AppendFormat(", {0}=\"{1}\"", urlEncode(sig.Key), urlEncode(sig.Value));
            return qsString;
        }
        private StringBuilder dictionaryToQSStringBuilder(Dictionary<String, String> parms, bool includeOAuth = false, bool passOAuthToken=true)
        {
            var qsString = new StringBuilder();

            foreach (var parm in parms.Where(i => includeOAuth || !i.Key.StartsWith("oauth")))
            {
                //if (!passOAuthToken && parm.Key.Equals("oauth_token"))
                //    continue;
                qsString.AppendFormat("{0}{1}={2}", qsString.Length == 0 ? "" : "&", urlEncode(parm.Key), urlEncode(parm.Value));
            }
            return qsString;
        }

        #endregion

        #region HttpHelpers

        //
        // Custom UrlEncode
        //
        protected virtual String customUrlEncode(String ToEncode)
        {
            String Encoded = "";

            for (int Index = 0; Index < ToEncode.Length; Index++)
            {
                char Test = ToEncode[Index];
                if ((Test >= 'A' && Test <= 'Z') ||
                    (Test >= 'a' && Test <= 'z') ||
                    (Test >= '0' && Test <= '9'))
                {
                    Encoded += Test;
                }
                else if (Test == '-' || Test == '_' || Test == '.' || Test == '~')
                {
                    Encoded += Test;
                }
                else
                {
                    Encoded += "%" + String.Format("{0:X}", (int)Test);
                }
            }
            return Encoded;
        }
        private string urlEncode(string toEncode)
        {
            if (upperCaseUrlEncoding)
                return customUrlEncode(toEncode);
            else
                return WebUtility.UrlEncode(toEncode);
        }
        protected virtual async Task<string> httpPostForString(string url, Dictionary<string, string> parms, string callbackUrl = "", bool? passOAuthToken = null, bool? passParamsInPost = null)
        {
            if (!passOAuthToken.HasValue)
                passOAuthToken = passOAuthTokenByDefault;

            var useHeader = usesAuthHeader || (useAuthHeaderForHttpUris && url.StartsWith("http:", StringComparison.OrdinalIgnoreCase));

            if (!String.IsNullOrEmpty(callbackUrl))
                parms.Add("oauth_callback", callbackUrl);

            var parms2 = injectOAuthSignatureIntoParms("POST", url, parms, passOAuthToken: passOAuthToken.Value);

            var qs = !passParamsInPost.HasValue || !passParamsInPost.Value ? dictionaryToQSStringBuilder(parms2, !useHeader, passOAuthToken.Value).ToString(): "";
            var postVars = passParamsInPost.HasValue && passParamsInPost.Value ? dictionaryToQSStringBuilder(parms2, !useHeader, passOAuthToken.Value).ToString() : "";
            var authHeader = useHeader ? dictionaryToOAuthHeaderStringBuilder(parms2, passOAuthToken.Value).ToString() : "";
            var postUrl = string.Format("{0}{1}{2}", url, qs.Length > 0 ? "?" : "", qs);
            string result = "";
#if DEBUG
            spitOutDebugInfo(url, !passParamsInPost.HasValue || !passParamsInPost.Value ? qs:postVars, authHeader);
#endif
            try
            {
                result = await HttpRequestHelper.simpleHttpPost(postUrl, postVars, authHeader);
                Debug.WriteLine(String.Format("httpPostForString --> {0}", result));
                return result;
            }
            catch (WebException ex)
            {
            }
            return result;
        }
        protected virtual async Task<string> httpGetForString(string url, Dictionary<string, string> parms, string callbackUrl = "", bool? passOAuthToken = null)
        {
            if (!passOAuthToken.HasValue)
                passOAuthToken = passOAuthTokenByDefault;
            var useHeader = usesAuthHeader || (useAuthHeaderForHttpUris && url.StartsWith("http:", StringComparison.OrdinalIgnoreCase));

            if (!String.IsNullOrEmpty(callbackUrl))
                parms.Add("oauth_callback", callbackUrl);

            var parms2 = injectOAuthSignatureIntoParms("GET", url, parms, passOAuthToken: passOAuthToken.Value);

            var qs = dictionaryToQSStringBuilder(parms2, !useHeader, passOAuthToken.Value).ToString();
            var authHeader = useHeader ? dictionaryToOAuthHeaderStringBuilder(parms2, passOAuthToken.Value).ToString() : "";

            var getUrl = string.Format("{0}{1}{2}", url, qs.Length > 0 ? "?" : "", qs);
            string result = "";
#if DEBUG
            spitOutDebugInfo(url, qs, authHeader);
#endif
            try
            {
                if(!useHeader)
                    result = await HttpRequestHelper.simpleHttpGet(getUrl);
                else
                    result = await HttpRequestHelper.simpleHttpGet(getUrl, authHeader);

                Debug.WriteLine(String.Format("httpGetForString --> {0}", result));
                return result;
            }
            catch (WebException ex)
            {
            }
            return result;
        }

        private void spitOutDebugInfo(string url, string qs, string authHeader)
        {
            Debug.WriteLine("baseUrl: " + url);
            if (qs.Length > 0)
            {
                Debug.WriteLine("?");
                var items = qs.Split('&');
                foreach (var item in items)
                    Debug.WriteLine("  " + item);
            }
            if (authHeader.Length > 0)
            {
                Debug.WriteLine("header: ");
                var items = authHeader.Split(',');
                foreach (var item in items)
                    Debug.WriteLine("  " + item);

            }
        }
        #endregion

        #region oAuth Signin

        public virtual async Task<OAuthToken> GetRequestTokenAsync(Dictionary<string, string> parms, string redirectUri="", int tries = 2)
        {

            String url = getRequestTokenURL; // +"?oauth_token=" + _apiKey; 

            try
            {
                string result;
                    
                if (getRequestTokenMethod.Equals("POST", StringComparison.OrdinalIgnoreCase))
                    result = await httpPostForString(url, new Dictionary<string,string>(), redirectUri, passOAuthToken:true, passParamsInPost: getRequestTokenParamsInPostContent);
                else
                    result = await httpGetForString(url, parms, redirectUri);
                if (string.IsNullOrEmpty(result) && tries > 1)
                {
                    return await GetRequestTokenAsync(parms, redirectUri, tries - 1);
                }
                RequestToken = new OAuthToken(result);

            }
            catch (WebException ex)
            {
                return null;
            }
            return RequestToken;
        }
        public virtual async Task<Boolean> AuthorizeAsync(string redirectUrl)
        {
            String url = await GetLoginUrlAsync(redirectUrl);
            if (String.IsNullOrEmpty(url))
                return false;

            Debug.WriteLine(String.Format("WebAuthBroker --> {0}", url));
#if WINDOWS_PHONE
            WebAuthenticationBrokerSettings.PageTweaks = authPageJsTweaks;
#endif

            var authResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, new Uri(url), new Uri(redirectUrl));
            if (authResult.ResponseStatus == WebAuthenticationStatus.Success || authResult.ResponseStatus == WebAuthenticationStatus.UserCancel)
            {
                Debug.WriteLine(String.Format("WebAuthData: {0}", authResult.ResponseData));
                var uriData = authResult.ResponseData.ToString().Split('?');
                var data = uriData.LastOrDefault();
                if (!usesOAuthVerifier)
                {
                    GetAccessTokenAsync(RequestToken.Token, "");
                }
                else if (!String.IsNullOrEmpty(data))
                {
                    // get access token
                    var parms = data.Split('&');
                    if (parms.Length > 1)
                    {
                        var tempToken = "";
                        var oAuthVerifier = "";
                        foreach (var pairs in parms)
                        {
                            var kvp = pairs.Split('=');
                            if (kvp.Length == 2)
                            {
                                switch (kvp[0].ToLowerInvariant())
                                {
                                    case "oauth_token":
                                        tempToken = kvp[1];
                                        break;
                                    case "oauth_verifier":
                                        oAuthVerifier = kvp[1];
                                        break;
                                }
                            }
                        }
                        if (!string.IsNullOrEmpty(tempToken) && !string.IsNullOrEmpty(oAuthVerifier))
                        {
                            return (await GetAccessTokenAsync(tempToken, oAuthVerifier)) != null;
                        }
                    }
                }
                return false;

                //var authToken = await GetAuthToken(redirectUrl);
                //return authToken != null;

            }
            else
                return false;
        }

        public virtual async Task<OAuthToken> GetAccessTokenAsync(string tempToken, string verifierToken, int tries = 2)
        {
            var url = getAuthTokenURL; 
            var parms = new Dictionary<string, string>();
            parms["oauth_token"] = tempToken;
            if (!String.IsNullOrEmpty(verifierToken))
            {
                parms["oauth_verifier"] = verifierToken;
            }
            var result = "";
            try
            {
                result = await httpPostForString(url, parms, passOAuthToken: true);
            }
            catch (Exception ex)
            {
                // ignore
            }
            if (string.IsNullOrEmpty(result) && tries > 1)
            {
                return await GetAccessTokenAsync(tempToken, verifierToken, tries - 1);
            }

            if (string.IsNullOrEmpty(result))
                return null;
            else
            {
                RequestToken = null;
                AccessToken = parseAuthToken(result);
                Debug.WriteLine("Access token: " + AccessToken.ToString());
                return AccessToken;
            }
        }

        protected virtual OAuthToken parseAuthToken(string result)
        {
            return new OAuthToken(result);
        }

        #endregion
    }
}
